﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;

namespace FlightToolkit.SimProp
{
    internal class NamespaceDef
    {
        #region Internal
        internal NamespaceDef(Symbols symbols, XElement def, out List<string> includeFiles)
        {
            if (!String.Equals(def.Name.LocalName, "SymbolDef"))
            {
                throw new InvalidDataException("Error parsing propdef, SymbolDef is missing.");
            }

            this.symbols = symbols;
            this.Name = def.Attribute("name").Value;

            includeFiles = new List<string>();

            var symbolIncludes = def.Descendants("SymbolInclude");
            foreach (var symInc in symbolIncludes)
            {
                string file = symInc.Attribute("filename").Value;
                Guid includeGuid = Guid.Parse(symInc.Attribute("id").Value);

                if (file != String.Empty && includeGuid != Guid.Empty)
                {
                    includeFiles.Add(file);
                }
                else
                {
                    throw new InvalidDataException("Error parsing SymbolInclude");
                }
            }
        }

        internal void Validate(XElement def, List<NamespaceDef> includeNamespaceDefs)
        {
            this.Includes = includeNamespaceDefs;

            var propertyDefs = def.Descendants("PropertyDef");
            this.PropertyDefs = new Dictionary<Guid, SymbolDef>();
            foreach (var propDef in propertyDefs)
            {
                var currentPropertyDef = new PropertyDef(symbols, this, propDef);
                this.PropertyDefs.Add(currentPropertyDef.Id, currentPropertyDef);
            }

            // A typo in one of the FSX propdefs requires us to use this work around
            // to avoid a validation later in the processing.
            // TODO: Should we also check what the specific namespace is to reduce the
            // chances of someone making the typo in another new propdef?
            var setDefs = def.Descendants("SetDef").Union(def.Descendants("SetDev"));
            this.SetDefs = new Dictionary<Guid, SymbolDef>();
            foreach (var setDef in setDefs)
            {
                var currentSetDef = new PropertySetDef(symbols, this, setDef);
                this.SetDefs.Add(currentSetDef.Id, currentSetDef);
            }

            // Validate all the property names:
            foreach (var setDef in this.SetDefs)
            {
                PropertySetDef set = (PropertySetDef)setDef.Value;
                if (set.propertyNames != null)
                {
                    foreach (string name in set.propertyNames)
                    {
                        Guid propertyID = this.LookupSymbolIDByName(name);
                        if (propertyID != Guid.Empty)
                        {
                            set.properties.Add(propertyID);
                        }
                        else
                        {
                            throw new InvalidDataException(String.Format("Warning: Unknown property '{0}' in set '{1}' while processing symbol file '{2}'", name, set.Name, this.Name));
                        }
                    }

                    // Don't null this out because it makes it easier to do validation.
                    //set.propertyNames = null;
                }
            }
        }

        internal string Name { get; private set; }
        internal Dictionary<Guid, SymbolDef> PropertyDefs { get; private set; }
        internal Dictionary<Guid, SymbolDef> SetDefs { get; private set; }

        internal SymbolDef GetLocalSymbolDef(Guid guid)
        {
            SymbolDef symbolDef;

            if (this.PropertyDefs.TryGetValue(guid, out symbolDef))
            {
                return symbolDef;
            }

            if (this.SetDefs.TryGetValue(guid, out symbolDef))
            {
                return symbolDef;
            }

            return null;
        }

        internal SymbolDef GetSymbolDef(Guid guid)
        {
            SymbolDef symbolDef = GetLocalSymbolDef(guid);
            if (symbolDef != null)
            {
                return symbolDef;
            }

            foreach (var include in this.Includes)
            {
                symbolDef = include.GetSymbolDef(guid);
                if (symbolDef != null)
                {
                    return symbolDef;
                }
            }

            return null;
        }

        internal SymbolDef GetSymbolDef(string name)
        {
            var result = this.PropertyDefs.Where(x => x.Value.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
            if (result.Count() > 0)
            {
                return result.First().Value;
            }

            var resultSet = this.SetDefs.Where(x => x.Value.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
            if (resultSet.Count() > 0)
            {
                return resultSet.First().Value;
            }

            foreach (var include in this.Includes)
            {
                var symbolDef = include.GetSymbolDef(name);
                if (symbolDef != null)
                {
                    return symbolDef;
                }
            }

            return null;
        }

        internal Guid LookupSymbolIDByName(string name)
        {
            var propertyResult = this.PropertyDefs.Where(x => x.Value.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
            if (propertyResult.Count() > 0)
            {
                return propertyResult.First().Key;
            }

            var setResult = this.SetDefs.Where(x => x.Value.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
            if (setResult.Count() > 0)
            {
                return setResult.First().Key;
            }

            foreach (var include in this.Includes)
            {
                Guid propertyID = include.LookupSymbolIDByName(name);
                if (propertyID != Guid.Empty)
                {
                    return propertyID;
                }
            }

            return Guid.Empty;
        }

        internal PropertySetDef LookupSetDefByName(string name)
        {
            var setResult = this.SetDefs.Where(x => x.Value.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
            if (setResult.Count() > 0)
            {
                return (PropertySetDef)setResult.First().Value;
            }

            foreach (var include in this.Includes)
            {
                PropertySetDef set = include.LookupSetDefByName(name);
                if (set != null)
                {
                    return set;
                }
            }

            return null;
        }

        internal void GetNamespaceAndName(string name, out NamespaceDef newNamespace, out string newName)
        {
            var strings = name.Split('.');
            if (strings.Count() == 2)
            {
                newNamespace = this.symbols.GetNamespaceDef(strings[0]);
                newName = strings[1];
            }
            else if (strings.Count() == 1)
            {
                newNamespace = this;
                newName = strings[0];
            }
            else
            {
                throw new ArgumentException("Invalid name", "name");
            }
        }
        #endregion

        #region Private
        private Symbols symbols; // Symbols containing this namespace
        private List<NamespaceDef> Includes;
        #endregion
    }
}
